package us.versus.them.b3ge.phyxside;

import flash.display.Loader;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;

import phx.Vector;

/**
 *
 * This class loads a Worldly object from an SVG
 * file.
 *
 * It uses a number of kooky conventions:
 * <OL>
 * <LI>movable blocks should have the style: fill:#0000ff</LI>
 * <LI>text elements are "directives"</LI>
 * <LI>supported directives are: start, goal and gravity</LI>
 * <LI>the x,y values of directives are used</LI>
 * <LI>the current convention for goals is to load the next board (numerically)</LI>
 * </OL>
 *
 * The SVG parsing uses a lot of heuristics and is pretty silly.
 * The main goal is to support creation of boards in Inkscape...
 *
 */
class Board {
	var worldly : Worldly;

	static var OBJECT_IDENTIFIER = "fill:#0000ff";

	public function isObject( xml : Xml ) {
		return isObjectStyle( xml.get( 'style' ) );
	}

	public function isObjectStyle( style : String ) {
		return (
			if ( null == style ) {
				false;
			} else {
				-1  != style.indexOf( OBJECT_IDENTIFIER );
			}
		);
	}

	public function new( worldly : Worldly, ?filename : String ) {
		this.worldly = worldly;
		var loader : URLLoader = new URLLoader();
		loader.addEventListener( Event.COMPLETE, onLoad );
		loader.load( new URLRequest( filename ) );
	}

	public function onLoad( event : Event ) : Void {
		try {
			worldly.gravityTo( 0, 0.9 );
			this.parse( Xml.parse( event.target.data ) );
		} catch( e:Dynamic ) {
			trace( 'load error: ' + e );
		}
		worldly.useWorldLoop();
	}

	public function parse( xml : Xml ) {
		//trace( 'parse ' + xml );
		for ( svg in xml.elementsNamed( 'svg:svg' ) ) {
			this.parseSvg( svg );
		}
	}

	public function parseSvg( svg : Xml ) {
		for ( g in svg.elementsNamed( 'svg:g' ) ) {
			this.parseG( g );
		}
	}

	public function parseG( g : Xml ) {
		for ( rect in g.elementsNamed( 'svg:rect' ) ) this.parseRect( rect );
		for ( text in g.elementsNamed( 'svg:text' ) ) this.parseText( text );
		for ( path in g.elementsNamed( 'svg:path' ) ) this.parsePath( path );
	}

	public function parseRect( rect : Xml ) {
		this.addRect( 
			  f( rect.get( 'x' ) )
			, f( rect.get( 'y' ) )
			, f( rect.get( 'width' ) )
			, f( rect.get( 'height' ) )
			, rect.get( 'transform' )
			, isObject( rect )
		);
	}

	public function parseText( text : Xml ) {
		var literal = '';
		for ( tspan in text.elementsNamed( 'tspan' ) ) {
			for ( content in tspan.iterator() ) {
				literal += content + '\n';
			}
		}
		literal = StringTools.trim( literal );
		this.parseDirective( f( text.get( 'x' ) ), f( text.get( 'y' ) ), literal );
	}

	public function parsePath( path : Xml ) {
		this.addPath( path.get( 'd' ), path.get( 'transform' ), isObject( path ) );
	}

	public function addRect( x:Float, y:Float, width:Float, height:Float, transform:String, obj:Bool ) {
		var tv = this.parseDumbSvgTranform( transform );
		if ( null != tv ) {
			var tmp = new phx.Vector( x, y );
			transformer( tmp, tv );
			x = tmp.x;
			y = tmp.y;

			tmp = new phx.Vector( width, height );
			transformer( tmp, tv );
			width = tmp.x;
			height = tmp.y;
		}
		if ( obj ) {
			worldly.addBox( x, y, width, height );
		} else {
			worldly.addFloor( x, y, width, height );
		}
	}

	public function addPath( d:String, transform:String, obj:Bool ) {
		var vl = this.parseDumbSvgPath( d );
		if ( null != vl ) {
			var tv = this.parseDumbSvgTranform( transform );
			if ( null != tv ) {
				for ( v in vl ) { 
					transformer( v, tv );
				}
			}
			if ( obj ) {
				worldly.addPolyBox( vl );
			} else {
				worldly.addPolyFloor( vl );
			}
		}
	}

	// override this method to handle different directives
	public function parseDirective( x:Float, y:Float, text:String ) {
		// TODO: better parser
		if ( 'start' == text ) {
			worldly.bobTo( x, y );
		} else
		if ( 'goal' == text ) {
			worldly.goalTo( x, y );
		} else
		if ( 'gravity' == text ) {
			worldly.gravityTo( x, y );
		} else {
			trace( 'unknown directive ' + text + ' at ' + x + ',' + y );
		}
	}

	public function f( s : String ) : Float {
		return Std.parseFloat( s );
	}

	////

	// CAREFUL!!!! modifies vector in place!!!
	public function transformer( vector:phx.Vector, matrix:Array< Float > ) {
		var x = vector.x * matrix[ 0 ] + vector.y * matrix[ 2 ] + matrix[ 4 ];
		var y = vector.x * matrix[ 1 ] + vector.y * matrix[ 3 ] + matrix[ 5 ];
		vector.set( x, y );
		// a=0 b=1 c=2 d=3 e=4 f=5
		// <xsl:value-of select="$x * $a + $y * $c + $e"/>
		// <xsl:value-of select="$x * $b + $y * $d + $f"/>
		return vector;
	}

	public function parseDumbSvgTranform( transform:String ) : Array< Float > {
		var lame = new Array< Float >();
		if ( null != transform ) {
			lame.push( 1 ); // sx            : a=0
			lame.push( 0 ); // scale x for y : b=1
			lame.push( 0 ); // scale y for x : c=2
			lame.push( 1 ); // sy            : d=3
			lame.push( 0 ); // tx            : e=4
			lame.push( 0 ); // ty            : f=5

			transform = ~/ +/.replace( transform, '' );
			transform = ~/scale/.replace( transform, ' scale' );
			transform = ~/matrix/.replace( transform, ' matrix' );
			transform = ~/rotate/.replace( transform, ' rotate' );
			transform = ~/translate/.replace( transform, ' translate' );
			transform = ~/^ /.replace( transform, '' );
			var transforms = transform.split( ' ' );
			for ( transform in transforms ) {
				var op = ~/\(.*/.replace( transform, '' );
				var args = ~/ +/.replace(
					~/\).*/.replace( ~/.*\(/.replace( transform, '' ), '' )
					, ''
				).split( ',' );
				//trace( '> ' + transform + ': ' + op + ' x ' + args );
				switch( op ) {
					case 'matrix': 
						var i = 0;
						for ( arg in args ) lame[ i++ ] = f( arg );
					case 'scale':
						lame[ 0 ] *= f( args[ 0 ] );
						if ( 2 == args.length ) {
							lame[ 3 ] *= f( args[ 1 ] );
						} else {
							lame[ 3 ] *= f( args[ 0 ] );
						}
				}
			}
		}
		if ( 6 != lame.length ) {
			lame = null;
		}
		return lame;
	}

	public function parseDumbSvgPath( d:String ) : Array< phx.Vector > {
		var vl = new Array< phx.Vector >();

		// normalize spacing:
		d = ~/([MLHVCSQTAZ])/g.replace( d, '$1 ' );
		d = ~/[, ]+/g.replace( d, ' ' );
		d = StringTools.trim( d );
		var args = d.split( ' ' );

		var i = 0;
		while ( i < args.length ) {
			var arg = args[ i ];
			var up = arg.toUpperCase();
			var x:Float;
			var y:Float;
			switch ( up ) {
				case 'M': // M = moveto
					x = f( args[ ++i ] );
					y = f( args[ ++i ] );
					// TODO: differentiate 'M' from 'm'
					vl.push( new phx.Vector( x, y ) );
				case 'L': // L = lineto
					x = f( args[ ++i ] );
					y = f( args[ ++i ] );
					// TODO: differentiate 'L' from 'l'
					vl.push( new phx.Vector( x, y ) );
				case 'Z': // Z = closepath
					// TODO: differentiate 'Z' from 'z'
				default:
					// C = curveto
					// H = horizontal lineto
					// V = vertical lineto
					// S = smooth curveto
					// Q = quadratic Belzier curve
					// T = smooth quadratic Belzier curveto
					// A = elliptical Arc
					//trace( 'unsupported path command ' + arg );
					vl = null;
					break;
			}
			i++;
		}
		return vl;
	}

}
